/*
 *  MiceCode.m
 *  micecode
 *
 * Copyright (c) 2011, Anton Babushkin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * MiceCode designed as memory and speed effective alternative to JSON, optimized
 * for modern object-oriented languages like Python or Java. It's more powerful
 * than Bencode or BSON, and more effective than JSON. Protocol ideology
 * influenced by JSON, Bencode, FAST. MiceCode have the following data types:
 * Integer, Float, Boolean, None, String, Bytes, List, Map, Decimal. Floats, Integers and
 * Decimals are stored in the smallest possible representation that doesn't worse
 * precision. Protocol supports integers up to 2^64 bytes, floats up to 64 bits and
 * string/bytes length up to 2^64. This realization limitations:
 * floats up to 64 bits only (double).
 *
 * Version: 1.0
 */

#import "MiceCode.h"

#define MICE_CODE_SPECIAL   0x00
#define MICE_CODE_INTEGER   0x10
#define MICE_CODE_FLOAT     0x20
#define MICE_CODE_BYTES     0x30
#define MICE_CODE_STRING    0x40
#define MICE_CODE_LIST      0x50
#define MICE_CODE_MAP       0x60
#define MICE_CODE_DECIMAL   0x70

static MCBoolean *_trueValue = nil;
static MCBoolean *_falseValue = nil;

@implementation MCBoolean

@synthesize booleanValue;

+ (void)load {
    _falseValue = [[MCBoolean alloc] init];
    _falseValue.booleanValue = false;
    _trueValue = [[MCBoolean alloc] init];
    _trueValue.booleanValue = true;
}

+ (MCBoolean *)falseValue {
    return _falseValue;
}

+ (MCBoolean *)trueValue {
    return _trueValue;
}

+ (MCBoolean *)booleanFromInt:(int)v {
    return v == 0 ? _falseValue : _trueValue;
}

- (id)copyWithZone:(NSZone *)zone {
    return [self retain];
}

- (BOOL)isEqual:(id)object {
    if ([object isKindOfClass:[MCBoolean class]]) {
        return booleanValue == ((MCBoolean *) object).booleanValue;
    }
    else {
        return false;
    }
}

- (NSString *)description {
    if (booleanValue)
        return @"True";
    else
        return @"False";
}

- (NSString *)stringValue {
    if (booleanValue)
        return @"True";
    else
        return @"False";
}

@end

NSObject *miceDecode(NSData *data) {
    unsigned char *bytes = (unsigned char *) [data bytes];
    NSUInteger len = [data length];
    NSUInteger idxValue = 0;
    return miceDecodeObject(bytes, len, &idxValue);
}

NSObject *miceDecodeObject(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger start = *idx;
    if (start >= len) {
        [NSException raise:@"MCEndOfStream"
                    format:@"Unexpected end of stream"];
    }
    unsigned char t = bytes[start] & 0xF0;    // Object Type
    if (t == MICE_CODE_SPECIAL) {
        unsigned char v = bytes[start] & 0x0F;
        (*idx)++;
        if (v == 0)
            return [MCBoolean falseValue];
        else if (v == 1)
            return [MCBoolean trueValue];
        else if (v == 0x0F)
            return [NSNull null];
        else
            [NSException raise:@"MCBadStream"
                        format:@"Unknown special object: 0x%02X", v];
    } else if (t == MICE_CODE_INTEGER)
        return [NSNumber numberWithLongLong:miceDecodeInteger(bytes, len, idx)];
    else if (t == MICE_CODE_FLOAT)
        return miceDecodeFloat(bytes, len, idx);
    else if (t == MICE_CODE_BYTES)
        return miceDecodeBytes(bytes, len, idx);
    else if (t == MICE_CODE_STRING)
        return miceDecodeString(bytes, len, idx);
    else if (t == MICE_CODE_LIST)
        return miceDecodeList(bytes, len, idx);
    else if (t == MICE_CODE_MAP)
        return miceDecodeMap(bytes, len, idx);
    else if (t == MICE_CODE_DECIMAL)
        return miceDecodeDecimal(bytes, len, idx);
    else
        [NSException raise:@"MCBadStream"
                    format:@"Unknown object type: 0x%02X", t];
    return nil;
}

NSUInteger miceDecodeSize(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger start = *idx;
    if (start >= len)
        [NSException raise:@"MCEndOfStream"
                    format:@"Unexpected end of stream"];
    unsigned char s = bytes[start] & 0x0f;
    if (s <= 0x08) {
        (*idx)++;
        return s;
    } else {
        int sizeBytes = (s & 0x07);
        if (sizeBytes > 4)
            [NSException raise:@"MCUnsupportedFormat"
                        format:@"Size field > 4 bytes not supported"];
        NSUInteger end = start + 1 + (NSUInteger) sizeBytes;
        if (end > len)
            [NSException raise:@"MCEndOfStream"
                        format:@"Unexpected end of stream"];
        NSUInteger res = 0;
        for (int i = 0; i < sizeBytes; i++)
            res |= ((NSUInteger) bytes[start + (NSUInteger) (sizeBytes - i)]) << i * 8;
        *idx = end;
        res += 0x09;
        return res;
    }
}

long long int miceDecodeInteger(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger ptr = *idx;
    NSUInteger size = miceDecodeSize(bytes, len, idx) + 1;
    if (size > 8)
        [NSException raise:@"MCUnsupportedFormat"
                    format:@"Integers > 64bit are not supported"];
    if (ptr + (NSUInteger) size >= len)
        [NSException raise:@"MCEndOfStream"
                    format:@"Unexpected end of stream"];
    long long int res = 0;
    for (int i = 0; i < size; i++)
        res |= (bytes[ptr + (NSUInteger) (size - i)] & 0xFFl) << (i * 8);
    *idx = ptr + 1 + (NSUInteger) size;
    if (((bytes[ptr + 1] & 0x80) == 0) || (size == 8))        // Positive number
        return res;
    else
        return res - ((long long int) 1 << (size * 8));
}

NSNumber *miceDecodeFloat(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger start = *idx;
    unsigned char s = bytes[start] & 0x0f;
    if (s == 0x03) {    // Single presision
        int32_t r = 0;
        start++;
        NSUInteger end = start + 4;
        if (end > len) {
            [NSException raise:@"MCEndOfStream"
                        format:@"Unexpected end of stream"];
        }
        for (int i = 0; i < 4; i++)
            r |= (bytes[start + (NSUInteger) (3 - i)] & 0xFF) << i * 8;
        *idx = end;
        return [NSNumber numberWithFloat:*((float *) &r)];
    }
    else if (s == 0x07) {    // Double presision
        int64_t r = 0;
        start++;
        NSUInteger end = start + 8;
        if (end > len) {
            [NSException raise:@"MCEndOfStream"
                        format:@"Unexpected end of stream"];
        }
        for (int i = 0; i < 8; i++) {
            r |= (int64_t) bytes[start + (NSUInteger) (7 - i)] << i * 8;
        }
        *idx = end;
        return [NSNumber numberWithDouble:*((double *) &r)];
    }
    else {
        [NSException raise:@"MCUnsupportedFormat"
                    format:@"Float size not supported: %i", s + 1];
    }
    return nil;
}

NSString *miceDecodeString(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger size = miceDecodeSize(bytes, len, idx);
    NSUInteger start = *idx;
    NSUInteger end = start + size;
    if (end > len) {
        [NSException raise:@"MCEndOfStream"
                    format:@"Unexpected end of stream"];
    }
    *idx = end;
    return [[[NSString alloc] initWithBytes:&bytes[start] length:size encoding:NSUTF8StringEncoding] autorelease];
}

NSData *miceDecodeBytes(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger size = miceDecodeSize(bytes, len, idx);
    NSUInteger start = *idx;
    NSUInteger end = start + size;
    if (end > len) {
        [NSException raise:@"MCEndOfStream"
                    format:@"Unexpected end of stream"];
    }
    *idx = end;
    return [NSData dataWithBytes:&bytes[start] length:size];
}

NSArray *miceDecodeList(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger size = miceDecodeSize(bytes, len, idx);
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:size];
    for (NSUInteger i = 0; i < size; i++) {
        [array addObject:miceDecodeObject(bytes, len, idx)];
    }
    return array;
}

NSDictionary *miceDecodeMap(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    NSUInteger size = miceDecodeSize(bytes, len, idx);
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:size];
    for (NSUInteger i = 0; i < size; i++) {
        NSString *k = miceDecodeString(bytes, len, idx);
        [dict setObject:miceDecodeObject(bytes, len, idx) forKey:k];
    }
    return dict;
}

NSDecimalNumber *miceDecodeDecimal(unsigned char *bytes, NSUInteger len, NSUInteger *idx) {
    long long int m = miceDecodeInteger(bytes, len, idx);
    NSUInteger ptr = *idx;
    int e = (char) bytes[ptr++];
    if ((e & 0x80) != 0) {      // Multi-byte scale
        int sbl = (e & 0x7f) + 1;
        if (sbl > 4)
            [NSException raise:@"MCOverflow" format:@"Decimal scale > 32bit is not supported"];
        NSUInteger end = ptr + sbl;
        e = 0;
        for (int i = 0; i < sbl; i++)
            e |= (bytes[end - 1 - i] & 0xFF) << i * 8;
        if (bytes[ptr] & 0x80)
            e = -0x140 + e;
        else
            e += 0x40;
        ptr = end;
    } else {                    // Single-byte scale
        if (e >= 0x40)
            e = -0x80 + e;
    }
    *idx = ptr;
    if (m < 0)
        return [NSDecimalNumber decimalNumberWithMantissa:-m exponent:(short) e isNegative:YES];
    else
        return [NSDecimalNumber decimalNumberWithMantissa:m exponent:(short) e isNegative:NO];
}

NSData *miceEncode(NSObject *obj) {
    NSMutableData *data = [NSMutableData data];
    miceEncodeObject(data, obj);
    return data;
}

void miceEncodeObject(NSMutableData *data, NSObject *obj) {
    if ([obj isKindOfClass:[NSNull class]]) {
        const char byteValue = 0x0f;
        [data appendBytes:&byteValue length:1];
    } else if ([obj isKindOfClass:[MCBoolean class]]) {
        char byteValue = ((MCBoolean *) obj).booleanValue;
        [data appendBytes:&byteValue length:1];
    } else if ([obj isKindOfClass:[NSNumber class]]) {
        if ([obj isKindOfClass:[NSDecimalNumber class]]) {
            miceEncodeDecimal(data, (NSDecimalNumber *) obj);
        } else {
            const char *t = [(NSNumber *) obj objCType];
            if ((t[0] == 'f') || (t[0] == 'd'))
                miceEncodeFloat(data, (NSNumber *) obj);
            else
                miceEncodeInteger(data, [(NSNumber *) obj longLongValue], MICE_CODE_INTEGER);
        }
    } else if ([obj isKindOfClass:[NSData class]]) {
        miceEncodeBytes(data, (NSData *) obj);
    } else if ([obj isKindOfClass:[NSString class]]) {
        miceEncodeString(data, (NSString *) obj);
    } else if ([obj isKindOfClass:[NSArray class]]) {
        miceEncodeList(data, (NSArray *) obj);
    } else if ([obj isKindOfClass:[NSDictionary class]]) {
        miceEncodeMap(data, (NSDictionary *) obj);
    } else {
        [NSException raise:@"MCInvalidObject"
                    format:@"Objects of class %@ are not supported: %@", [obj className], obj];
    }
}

void miceEncodeSize(NSMutableData *data, char type, NSUInteger size) {
    char res[5];
    int n = 0;
    res[0] = type;
    if (size <= 8) {
        res[0] |= size;
    } else {
        size -= 9;
        for (n = 1; n < 5; n++)
            if (size < (1 << (n * 8)))
                break;
        res[0] |= 0x08 + n;
        for (int i = 0; i < n; i++)
            res[n - i] = (char) ((size >> (i * 8)) & 0xFF);
    }
    [data appendBytes:res length:(NSUInteger) (n + 1)];
}

void miceEncodeInteger(NSMutableData *data, long long int obj, char type) {
    char res[8];
    NSUInteger n;
    long long int abs_inum;
    if (obj >= 0)
        abs_inum = obj;
    else
        abs_inum = -1 - obj;
    for (n = 0; n < 7; n++) {    // n == size - 1
        if (abs_inum < (0x80l << (n * 8)))
            break;
    }
    miceEncodeSize(data, type, n);
    n++;
    for (int i = 0; i < n; i++)
        res[n - 1 - i] = (char) ((obj >> i * 8) & 0xFF);
    [data appendBytes:res length:n];
}

void miceEncodeFloat(NSMutableData *data, NSNumber *obj) {
    char res[9];
    float fnum = [obj floatValue];
    double dnum = [obj doubleValue];
    if ((fnum == dnum) || isnan(dnum) || isinf(dnum)) {
        int32_t inum = *((int32_t *) &fnum);
        res[0] = MICE_CODE_FLOAT | 0x03;
        for (int i = 0; i < 4; i++)
            res[4 - i] = (char) ((inum >> i * 8) & 0xFF);
        [data appendBytes:res length:5];
    }
    else {
        int64_t inum = *((int64_t *) &dnum);
        res[0] = MICE_CODE_FLOAT | 0x07;
        for (int i = 0; i < 8; i++)
            res[8 - i] = (char) ((inum >> i * 8) & 0xFFl);
        [data appendBytes:res length:9];
    }
}

void miceEncodeBytes(NSMutableData *data, NSData *obj) {
    miceEncodeSize(data, MICE_CODE_BYTES, [obj length]);
    [data appendData:obj];
}

void miceEncodeString(NSMutableData *data, NSString *obj) {
    NSData *body = [obj dataUsingEncoding:NSUTF8StringEncoding];
    miceEncodeSize(data, MICE_CODE_STRING, [body length]);
    [data appendData:body];
}

void miceEncodeList(NSMutableData *data, NSArray *obj) {
    miceEncodeSize(data, MICE_CODE_LIST, [obj count]);
    for (NSObject *i in obj) {
        miceEncodeObject(data, i);
    }
}

void miceEncodeMap(NSMutableData *data, NSDictionary *obj) {
    miceEncodeSize(data, MICE_CODE_MAP, [obj count]);
    for (NSObject *k in [obj keyEnumerator]) {
        if (![k isKindOfClass:[NSString class]]) {
            [NSException raise:@"MCInvalidObject"
                        format:@"Map key must be NSString, not %@", [k className]];
        }
        miceEncodeString(data, (NSString *) k);
        miceEncodeObject(data, [obj objectForKey:k]);
    }
}

void miceEncodeDecimal(NSMutableData *data, NSDecimalNumber *obj) {
    NSDecimal dec = [obj decimalValue];
    long long int m = [[obj decimalNumberByMultiplyingByPowerOf10:(short) -dec._exponent] longLongValue];
    miceEncodeInteger(data, m, MICE_CODE_DECIMAL);
    int e = dec._exponent;
    if (e >= -0x40 && e < 0x40) {    // Single-byte scale
        char res[1];
        res[0] = (char) (e & 0x7f);
        [data appendBytes:res length:1];
    } else {                        // Multi-byte scale
        if (e >= 0)
            e -= 0x40;
        else
            e += 0x40;
        int abs_e;
        if (e >= 0)
            abs_e = e;
        else
            abs_e = -1 - e;
        NSUInteger n;
        for (n = 0; n < 3; n++) {    // n == size - 1
            if (abs_e < (0x80l << (n * 8)))
                break;
        }
        char res[5];
        res[0] = (char) (0x80 | n);
        n++;
        for (int i = 0; i < n; i++)
            res[n - i] = (char) ((e >> i * 8) & 0xFF);
        [data appendBytes:res length:(NSUInteger) (n + 1)];
    }
}
